﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

namespace SimpleIR
{
	/// <summary>
	/// A representation for interning and tracking all the program invocation signatures.
	/// </summary>
	public sealed class IRInvocationTable
	{
		private readonly List<IRStaticInvoke> m_singletonStaticInvokes;
		private readonly List<IRVirtualInvoke> m_singletonVirtualInvokes;

		private readonly Dictionary<IRType, Dictionary<IRInvokeSig, IRInvokeIdentity>> m_invokeInternTable;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_singletonStaticInvokes != null);
			Contract.Invariant(this.m_singletonVirtualInvokes != null);
			Contract.Invariant(this.m_invokeInternTable != null);
		}

		public IRInvocationTable()
		{
			this.m_singletonStaticInvokes = new List<IRStaticInvoke>();
			this.m_singletonVirtualInvokes = new List<IRVirtualInvoke>();
			this.m_invokeInternTable = new Dictionary<IRType, Dictionary<IRInvokeSig, IRInvokeIdentity>>();
		}

		///////////////////////////////////////////////
		//Invocation Signatures

		/// <summary>
		/// Intern a static invocation sig.
		/// </summary>
		public IRStaticInvoke InternStaticInvoke(IRType defInClass, String name, List<IRType> sigtypes)
		{
			Contract.Requires(defInClass != null);
			Contract.Requires(!String.IsNullOrEmpty(name));
			Contract.Requires(sigtypes != null && (!sigtypes.Contains(null)));
			Contract.Ensures(Contract.Result<IRStaticInvoke>() != null);

			IRStaticInvoke ssi = this.m_singletonStaticInvokes.Find((IRStaticInvoke tsi) => StaticInvokeMatches(tsi, defInClass, name, sigtypes));
			if(ssi == null)
			{
				ssi = new IRStaticInvoke(defInClass, name, sigtypes);
				this.m_singletonStaticInvokes.Add(ssi);
			}

			return ssi;
		}

		/// <summary>
		/// Return true if the given static invoke represents the exact same signature as the various components provided.
		/// </summary>
		[Pure]
		public static bool StaticInvokeMatches(IRStaticInvoke tsi, IRType defInClass, String name, List<IRType> sigtypes)
		{
			Contract.Requires(tsi != null);
			Contract.Requires(defInClass != null);
			Contract.Requires(!String.IsNullOrEmpty(name));
			Contract.Requires(sigtypes != null && (!sigtypes.Contains(null)));
			
			if(tsi.DefClass != defInClass || (!tsi.InvokeName.Equals(name)) || tsi.InvokeSig.Count != sigtypes.Count)
				return false;

			for(int i = 0; i < tsi.InvokeSig.Count; ++i)
			{
				if(tsi.InvokeSig[i] != sigtypes[i])
					return false;
			}

			return true;
		}

		/// <summary>
		/// Intern a member invocation sig.
		/// </summary>
		public IRVirtualInvoke InternVirtualInvoke(String name, List<IRType> sigtypes)
		{
			Contract.Requires(!String.IsNullOrEmpty(name));
			Contract.Requires(sigtypes != null && (!sigtypes.Contains(null)));
			Contract.Ensures(Contract.Result<IRVirtualInvoke>() != null);

			IRVirtualInvoke mvi = this.m_singletonVirtualInvokes.Find((IRVirtualInvoke tsi) => VirtualInvokeMatches(tsi, name, sigtypes));
			if(mvi == null)
			{
				mvi = new IRVirtualInvoke(name, sigtypes);
				this.m_singletonVirtualInvokes.Add(mvi);
			}

			return mvi;
		}

		/// <summary>
		/// Return true if the given virtual invoke represents the exact same signature as the various components provided.
		/// </summary>
		[Pure]
		public static bool VirtualInvokeMatches(IRVirtualInvoke tsi, String name, List<IRType> sigtypes)
		{
			Contract.Requires(tsi != null);
			Contract.Requires(!String.IsNullOrEmpty(name));
			Contract.Requires(sigtypes != null && (!sigtypes.Contains(null)));
			
			if((!tsi.InvokeName.Equals(name)) || tsi.InvokeSig.Count != sigtypes.Count)
				return false;

			for(int i = 0; i < tsi.InvokeSig.Count; ++i)
			{
				if(tsi.InvokeSig[i] != sigtypes[i])
					return false;
			}

			return true;
		}

		/// <summary>
		/// Get the main method for the program.
		/// </summary>
		[Pure]
		public IRStaticInvoke GetMainInvoke()
		{
			Contract.Ensures(Contract.Result<IRStaticInvoke>() != null);

			return this.m_singletonStaticInvokes.Find((IRStaticInvoke svi) => (svi.InvokeName.Equals("Main") && svi.InvokeSig.Count == 1));
		}

		///////////////////////////////////////////////
		//Invocation Identities

		public IRInvokeIdentity InternInvokeIdentity(IRType defClass, IRInvokeSig sig)
		{
			Contract.Requires(defClass != null);
			Contract.Requires(sig != null);
			Contract.Ensures(Contract.Result<IRInvokeIdentity>() != null);

			if(!this.m_invokeInternTable.ContainsKey(defClass))
				this.m_invokeInternTable.Add(defClass, new Dictionary<IRInvokeSig, IRInvokeIdentity>());

			if(!this.m_invokeInternTable[defClass].ContainsKey(sig))
			{
				IRInvokeIdentity iiv = new IRInvokeIdentity(defClass, sig);
				this.m_invokeInternTable[defClass].Add(sig, iiv);
			}

			return this.m_invokeInternTable[defClass][sig];
		}

		/// <summary>
		/// Return the Invocation Identity for the given signature and defining type.
		/// </summary>
		[Pure]
		public IRInvokeIdentity LookupInvokeIdentity(IRType tt, IRInvokeSig sig)
		{
			Contract.Requires(tt != null);
			Contract.Requires(sig != null);
			Contract.Ensures(Contract.Result<IRInvokeIdentity>() != null);

			return this.m_invokeInternTable[tt][sig]; 
		}

		/// <summary>
		/// Get all the static concstructors that are called on initialization.
		/// </summary>
		public List<IRStaticInvoke> GetAllNeededStaticConstructors()
		{
			Contract.Ensures(Contract.Result<List<IRStaticInvoke>>() != null && (!Contract.Result<List<IRStaticInvoke>>().Contains(null)));

			List<IRStaticInvoke> resl = new List<IRStaticInvoke>();
			foreach(Dictionary<IRInvokeSig, IRInvokeIdentity> dikv in this.m_invokeInternTable.Values)
			{
				foreach(IRInvokeSig sig in dikv.Keys)
				{
					if(sig.InvokeName.Equals(".cctor"))
						resl.Add((IRStaticInvoke)sig);
				}
			}

			return resl;
		}

		/// <summary>
		/// Compute a stable derterministic order for all invocations based on lexo-graphic compare.
		/// </summary>
		internal void ComputeInvokeIDStableOrder()
		{
			List<IRInvokeIdentity> iidl = new List<IRInvokeIdentity>();
			foreach(Dictionary<IRInvokeSig, IRInvokeIdentity> tpd in this.m_invokeInternTable.Values)
				iidl.AddRange(tpd.Values);

			IRInvokeIdentity.ComputeInvokeIDStableOrder(iidl);
		}
	}
}
